package com.alibaba.dubbo.remoting.transport.netty4;

import io.netty.buffer.ByteBuf;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;

public class Netty4ReadChannelBuffer extends AbstractNetty4ChannelBuffer implements
		com.alibaba.dubbo.remoting.buffer.ChannelBuffer {

	private int readerIndex = 0;
	private int markReaderIndex = 0;
	private int total = 0;
	
	public Netty4ReadChannelBuffer(){
		super();
	}

	public void add(ByteBuf buf) {
		buf.retain();
		Component b = new Component(buf, true);
		if (!components.isEmpty()) {
			Component last = components.get(components.size() - 1);
			b.offset = last.endOffset;
		} else {
			b.offset = 0;
		}
		b.endOffset = b.offset + b.length;
		components.add(b);
		this.total = b.endOffset;
	}

	/**
	 * release buffer
	 */
	public void compact() {
		int startIndex =  this.getComponentIndex(this.readerIndex);
		if(startIndex < 0){
			//all
			for(Component c : this.components){
				c.freeIfNecessary();
			}
			this.components.clear();
			this.total = 0;
			this.readerIndex = this.markReaderIndex = 0;
		}else if(startIndex >0){
			for(int i = 0;i<startIndex;i++){
				Component c = this.components.remove(0);
				c.freeIfNecessary();
			}
			
			Component c = this.components.get(0);
			this.readerIndex -= c.offset;
			this.markReaderIndex = 0;
			c.offset = 0;
			c.endOffset = c.offset + c.length;
			Component prev = c;
			for(int i= 1;i< this.components.size();i++){
				c = this.components.get(i);
				c.offset = prev.endOffset;
				c.endOffset = c.offset + c.length;
				prev = c;
			}
			this.total = prev.endOffset;
		}
	}


	@Override
	public void clear() {
		while (!components.isEmpty()) {
			Component buf = components.remove(0);
			buf.freeIfNecessary();
		}
		readerIndex = 0;
	}

	 
	@Override
	public void discardReadBytes() {
		clear();
	}

	@Override
	public void ensureWritableBytes(int writableBytes) {
		throw new java.lang.IllegalStateException();
	}


	@Override
	public byte getByte(int index) {
		Component buf = this.getComponent(index);
		return buf.buf.getByte(index - buf.offset);
	}

	@Override
	public void getBytes(int index, byte[] dst) {
		getBytes(index, dst, 0, dst.length);
	}

	@Override
	public void getBytes(int index, byte[] dst, int dstIndex, int length) {
		if(length == 0) return;
		if (index < 0 || index >= total)
			throw new java.lang.IndexOutOfBoundsException();
		int offset = 0;
		int count = this.components.size();
		int startIndex = this.getComponentIndex(index);
		Component b = this.components.get(startIndex);
		offset = index - b.offset;

		int len = length;
		int pos = dstIndex;
		while (startIndex < count && len > 0) {
			int l = Math.min(len, b.length - offset);
			if (offset > 0) {
				b.buf.markReaderIndex();
				b.buf.skipBytes(offset);
				b.buf.readBytes(dst, pos, l);
				b.buf.resetReaderIndex();
			} else {
				b.buf.readBytes(dst, pos, l);
			}
			len -= l;
			pos += l;
			startIndex++;
			offset = 0;
			if (startIndex < count) {
				b = this.components.get(startIndex);
			}
		}
	}

	@Override
	public void getBytes(int index, ByteBuffer dst) {
		throw new java.lang.IndexOutOfBoundsException();
	}

	@Override
	public void getBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst) {
		throw new java.lang.IndexOutOfBoundsException();

	}

	@Override
	public void getBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst, int length) {
		throw new java.lang.IndexOutOfBoundsException();
	}

	@Override
	public void getBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst, int dstIndex,
			int length) {
		throw new java.lang.IndexOutOfBoundsException();
	}

	@Override
	public void getBytes(int index, OutputStream dst, int length)
			throws IOException {
		if (index < 0 || index >= total)
			throw new java.lang.IndexOutOfBoundsException();
		int offset = 0;
		int count = this.components.size();
		int startIndex = this.getComponentIndex(index);
		Component b = this.components.get(startIndex);
		offset = index - b.offset;

		int len = length;
		while (startIndex < count && len > 0) {
			int l = Math.min(len, b.length - offset);
			b.buf.markReaderIndex();
			b.buf.skipBytes(offset);
			b.buf.readBytes(dst, l);
			b.buf.resetReaderIndex();
			len -= l;
			startIndex++;
			offset = 0;
			if (startIndex < count) {
				b = this.components.get(startIndex);
			}
		}
	}

	@Override
	public boolean isDirect() {
		return false;
	}

	@Override
	public void markReaderIndex() {
		markReaderIndex = this.readerIndex;
	}

	@Override
	public void markWriterIndex() {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public boolean readable() {
		return !this.components.isEmpty() && this.readerIndex < this.total;
	}

	@Override
	public int readableBytes() {
		return this.total - this.readerIndex;
	}

	@Override
	public byte readByte() {
		byte data = getByte(this.readerIndex);
		this.readerIndex++;
		return data;
	}

	@Override
	public void readBytes(byte[] dst) {
		readBytes(dst, 0, dst.length);
	}

	@Override
	public void readBytes(byte[] dst, int dstIndex, int length) {
		if(length == 0) return;
		int index = this.readerIndex;
		if (index < 0 || index >= total)
			throw new java.lang.IndexOutOfBoundsException();
		int offset = 0;
		int count = this.components.size();
		int startIndex = this.getComponentIndex(index);
		Component b = this.components.get(startIndex);
		offset = index - b.offset;

		int len = length;
		int pos = dstIndex;
		while (startIndex < count && len > 0) {
			int l = Math.min(len, b.length - offset);
			b.buf.markReaderIndex();
			b.buf.skipBytes(offset);
			b.buf.readBytes(dst, pos, l);
			b.buf.resetReaderIndex();
			len -= l;
			pos += l;
			startIndex++;
			offset = 0;
			if (startIndex < count) {
				b = this.components.get(startIndex);
			}
		}
		this.readerIndex += (pos - dstIndex);
	}

	@Override
	public void readBytes(ByteBuffer dst) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void readBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void readBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst,
			int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void readBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer dst,
			int dstIndex, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public com.alibaba.dubbo.remoting.buffer.ChannelBuffer readBytes(int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void resetReaderIndex() {
		this.readerIndex = this.markReaderIndex;
	}

	@Override
	public void resetWriterIndex() {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public int readerIndex() {
		return this.readerIndex;
	}

	@Override
	public void readerIndex(int readerIndex) {
		this.readerIndex = readerIndex;
	}

	@Override
	public void readBytes(OutputStream dst, int length) throws IOException {
		if(length == 0) return;
		int index = this.readerIndex;
		if (index < 0 || index >= total)
			throw new java.lang.IndexOutOfBoundsException();
		int offset = 0;
		int count = this.components.size();
		int startIndex = this.getComponentIndex(index);
		Component b = this.components.get(startIndex);
		offset = index - b.offset;

		int len = length;
		while (startIndex < count && len > 0) {
			int l = Math.min(len, b.length - offset);
			b.buf.markReaderIndex();
			b.buf.skipBytes(offset);
			b.buf.readBytes(dst, l);
			b.buf.resetReaderIndex();
			len -= l;
			startIndex++;
			offset = 0;
			if (startIndex < count) {
				b = this.components.get(startIndex);
			}
		}
		this.readerIndex += (length - len);
	}

	@Override
	public void setByte(int index, int value) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index, byte[] src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index, byte[] src, int srcIndex, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index, ByteBuffer src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer src, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setBytes(int index,
			com.alibaba.dubbo.remoting.buffer.ChannelBuffer src, int srcIndex,
			int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public int setBytes(int index, InputStream src, int length)
			throws IOException {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void setIndex(int readerIndex, int writerIndex) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void skipBytes(int length) {
		this.readerIndex += length;
		if(this.readerIndex > this.total){
			this.readerIndex = this.total;
		}
	}

	@Override
	public ByteBuffer toByteBuffer() {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public ByteBuffer toByteBuffer(int index, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public boolean writable() {
		return false;
	}

	@Override
	public int writableBytes() {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeByte(int value) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(byte[] src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(byte[] src, int index, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(ByteBuffer src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer src) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer src,
			int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writeBytes(com.alibaba.dubbo.remoting.buffer.ChannelBuffer src,
			int srcIndex, int length) {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public int writeBytes(InputStream src, int length) throws IOException {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public int writerIndex() {
		throw new java.lang.IllegalStateException();
	}

	@Override
	public void writerIndex(int writerIndex) {
		throw new java.lang.IllegalStateException();
	}
}
